package ar.com.sne.dao;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.classic.Session;

import ar.com.sne.bean.BaseBean;
import ar.com.sne.navigation.session.UserCredentialManager;

/**
 * Metodos para el uso de DAOs basados en Hibernate
 * @author TECSO
 * @author Andrei 
 */
public abstract class GenericAbstractDAO {
	
	private Logger log = Logger.getLogger(GenericAbstractDAO.class);
	
    protected Class bOClass;
   
    
    /* Este metodo debe implmentase de modo que retorno 
     * la session de hibernate a utilizar por el DAO.
     * Por lo general: <code>
     *   return XXXHibernateUtil.currentSession()
     * </code>
     */
    protected abstract Session currentSession();

    public GenericAbstractDAO(Class boClass) {
        this.bOClass = boClass;
        log.info("Instanciando clase: " + bOClass.getSimpleName());
    }

    public List getList() throws HibernateException {
        Session session = currentSession();
        log.info("Clase a listar: "+ bOClass.getSimpleName());
        try {
			return session.createQuery("FROM "+ bOClass.getSimpleName()).list();
		} catch (HibernateException e) {
			e.printStackTrace();
		}
		return null;
    }
    
    /**
     * Recupera un objecto de la base de datos dado su Id, 
     * si no existe arroja una HibernateException.
     * 
     * @param id
     * @return
     * @throws HibernateException
     */
    public Object getById(Long id) throws HibernateException {
    	return currentSession().load(bOClass, id);	
    	//return SweHibernateUtil.currentSession().load(bOClass, id);
    }
    
    /**
     * Recupera un objecto de la base de datos dado su Id, 
     * si no existe devuelve null.
     * 
     * @param id
     * @return
     * @throws HibernateException
     */
    public Object getByIdNull(Long id) throws HibernateException {
    	if (id == null)
    		return null;
    	else		
    		return currentSession().get(bOClass, id);
    }
    
    
    public List getList(Criteria criteria) throws HibernateException {
    	return criteria.list();
    }
    
    public List getList(Query query) throws HibernateException {
    	return query.list();
    }

    /**
     *  Obtiene el Id Serializado de un PreparedStatement.
     *  @param PreparedStatement
     *  @return Long
     */
    public long getSerial(PreparedStatement ps) throws Exception{
    	long runId = 0;
    	if(ps!=null){		
    		ResultSet rs = ps.getGeneratedKeys();
    		while (rs.next()) {
    			runId = rs.getInt(1);	 
    	    }
    		rs.close();
    	}
    	return runId;
    }
    public synchronized Long save(BaseBean o) throws HibernateException {
    	log.info("Preparando Insert clase: "+ bOClass.getSimpleName());
    	try {
    		String userName = UserCredentialManager.getIntance().getUser().getNombre();
    		if (userName != null) {
    			o.setUsuario(userName);
    		} else { 
    			o.setUsuario("SNe");
			}
    		o.setFechaMod(new Date());
    	} catch (Exception e) {}
    	
    	Long id = (Long) currentSession().save(o);
    	System.out.println("Insert exitoso: "+ bOClass.getName());
    	return id;
    }
    
    public synchronized void update(BaseBean o) throws HibernateException {
    	try {
    		
			String userName = UserCredentialManager.getIntance().getUser().getNombre();
    		if (userName != null) {
    			o.setUsuario(userName);
    		} else { 
    			o.setUsuario("SNe");
			}
    		o.setFechaMod(new Date());
    	} catch (Exception e) {}
    	currentSession().update(o);
    }
    
    public void delete(Object o) throws HibernateException {
    	log.info("Eliminar: " + o.getClass() + " id: " + o.toString() );
    	currentSession().delete(o);   
    }
    
    public int getCount(Criteria criteria) throws HibernateException {
    	return criteria.list().size();
    }
	
	public int getCount (Query query)throws HibernateException{
    	return query.list().size();
    }    
    
	public List getListOrdenada(String campoOrden)  throws HibernateException  {
		Session session = currentSession();
        return session.createQuery("from " + bOClass.getName() +" order by  " + campoOrden).list();
    }

	public List getListActiva()  throws HibernateException  {
		Session session = currentSession();
        return session.createQuery("from " + bOClass.getName() + " where estado = 1").list();
    }

	/**
	 * Obtiene la lista de resultados de la ejecucion de la consulta.
	 * 
	 * @param queryString HQL de la consulta a ejecutar
	 * @param maxResults  maxima ctd de resultados a obtener
	 * @param firstResult indice del 1er registro 
	 * @return List  lista de resultados obtenida
	 * @throws Exception
	 */
	public List executeSearch(String queryString, Integer maxResults, Integer firstResult) throws Exception{
		
		Session session = currentSession();
	    Query query = session.createQuery(queryString);
	    
	    if(maxResults != null){
	    	query.setMaxResults(maxResults);
	    }
	    if (firstResult != null){
	    	query.setFirstResult(firstResult);
	    }

		return  query.list();
	}

	/**
	 * Obtiene la lista de resultados de la ejecucion de la consulta sin paginar
	 * @param queryString
	 * @return List
	 * @throws Exception
	 */
	public List executeSearch(String queryString) throws Exception{
		return  executeSearch(queryString, null, null);
	}

	/**
	 * Retorna true si el parametro bo, posee hijos de la clase joinClass, relacionados mediante el parametro joinProperty.
	 * Este metodo requiere que exista previamente los mapeos de hibernate adecuados.
	 * <p>Ej: Para saber si una aplicacion posee usuarios asignados:
	 * <code>hasReference(apl, UsrApl.class, "aplicacion");</code>
	 * <br>donde apl es la instancia de la aplicacion que se desea saber si tiene o no usuarios.
	 * <br>donde UsrApl.class es la clase hija
	 * <br>"aplicacion" es el nombre de la propiedad que relaciona el hijo con el padre.
	 * <p>IMPLEMENTACION: este metodo simplemente terminado ejecutando el query: from UsrApl u where u.aplicacion = :apl
	*/
	public boolean hasReferenceGen(BaseBean bo, Class joinClass, String joinProperty) {
		String queryString = "from %s t where t.%s = :bo";
		Session session = currentSession();
		Query query = session.createQuery(String.format(queryString, joinClass.getName(), joinProperty));
		query.setEntity("bo", bo);
		query.setMaxResults(1);
		List list = (List) query.list();
		return list == null || list.isEmpty() ? false : true;
	}

	/**
	 * Retorna true si el parametro bo, posee hijos de la clase joinClass, relacionados mediante el parametro joinProperty y
	 * dichos Hijos posess estado == 1
	 * Este metodo requiere que exista previamente los mapeos de hibernate adecuados.
	 * <p>Ej: Para saber si una aplicacion posee usuarios asignados:
	 * <code>hasReference(apl, UsrApl.class, "aplicacion");</code>
	 * <br>donde apl es la instancia de la aplicacion que se desea saber si tiene o no usuarios.
	 * <br>donde UsrApl.class es la clase hija
	 * <br>"aplicacion" es el nombre de la propiedad que relaciona el hijo con el padre.
	 * <p>IMPLEMENTACION: este metodo simplemente terminado ejecutando el query: from UsrApl u where u.aplicacion = :apl and u.estado = 1
	*/
	public boolean hasReferenceActivoGen(BaseBean bo, Class joinClass, String joinProperty) {
		String queryString = "from %s t where t.%s = :bo and u.estado = 1";
		Session session = currentSession();
		Query query = session.createQuery(String.format(queryString, joinClass.getName(), joinProperty));
		query.setEntity("bo", bo);
    	List list = (List) query.list();
		return list == null || list.isEmpty() ? false : true;
	}

	private Object invokeGetter(String propName, Object obj) throws Exception {
		
		if(List.class.isInstance(obj)){
			return this.invokeGetter(propName, (List) obj);
		}
		
		String getterName = "get" + propName.substring(0,1).toUpperCase() + propName.substring(1);
		return obj.getClass().getMethod(getterName).invoke(obj, (Object[]) null);
	}
	
	private Object invokeGetter(String propName, List listObject) throws Exception {
		List resultados = new ArrayList();
		String getterName = "get" + propName.substring(0,1).toUpperCase() + propName.substring(1);
		for (Object object : listObject) {
			resultados.add(object.getClass().getMethod(getterName).invoke(object, (Object[]) null));
		}
		return resultados;
	}

	
	public Object invokeGetters(String propName, Object obj) throws Exception {
		if (!propName.contains(".")){
			return this.invokeGetter(propName, obj);
		}
		
		String[] propNames = propName.split("\\.");
		
		for (int i = 0; i < propNames.length; i++) {
			String pn = propNames[i];
			obj = this.invokeGetter(pn, obj);
			if(obj == null){
				break;	
			}
		}
		return obj;
	}

	/**
	 * Devuelve todos los BO cuyo id esten en el rango entre idDesde e idHasta.
	 * Admite parametros nulos.
	 * @param idDesde
	 * @param idHasta
	 * @return List
	 * @throws HibernateException
	 */
	public List getListByRangoIds(Long idDesde, Long idHasta)  throws HibernateException  {
		Session session = currentSession();
		String consulta = "FROM " + bOClass.getName();
		boolean flagAnd = false;
		
		if(idDesde != null){
			consulta += flagAnd ? " and " : " where ";
			consulta += " id >= " + idDesde;
			flagAnd = true;
		}
		if(idHasta != null){
			consulta += flagAnd ? " and " : " where ";
			consulta += " id <= " + idHasta;
			flagAnd = true;
		}
		
		return session.createQuery(consulta).list();
	}

}
